#include "GCrypt.h"
#include <iostream>
#include <string.h>
#include <QDebug>
using namespace std;

GCrypt::GCrypt()
    :pub_key(NULL), enc_key(NULL), aes_key(NULL), aes_key_len(32), cert_buf(NULL), private_key(NULL)
{
}

GCrypt::~GCrypt()
{
    //cout<<"SSL clear start"<<endl;
    if(pub_key != NULL)
        RSA_free(pub_key);
    if(private_key != NULL)
        RSA_free(private_key);
    free(cert_buf);
    if(aes_key != NULL)
        free(aes_key);
    if(enc_key != NULL)
        free(enc_key);
    //aes_key_len=0;
    //cout<<"SSL clear end"<<endl;
}

int GCrypt::Init(const char * cert_path)
{

    aes_key = (unsigned char *) malloc(aes_key_len);
    aes_key = generate_key();
	if(aes_key == NULL)
		return -1;
    qDebug()<<"1"<<cert_path;
        //cout<<"1"<<endl;
    //Get public key from certificate
    pub_key = get_cert_public_key(cert_path);
	if(pub_key == NULL)
		return -1;
    qDebug()<<"2"<<cert_path;
    //RSA_Encrypt()
    //enc_key = (unsigned char *)malloc(aes_key_len);
	enc_key=rsa_encrypt(aes_key, 32);
    if(enc_key == NULL)
		return -1;
    return 0;
}

int GCrypt::Init(const char * cert_path, const char * private_path)
{
    aes_key = NULL;
    enc_key = NULL;
    if(load_cert_from_file(cert_path) < 0)
        return -1;
    if(load_private_key_from_file(private_path) < 0)
        return -1;
    path_to_cert="";
    path_to_cert+=cert_path;
    return 0;
}

std::string GCrypt::get_cert_path()
{
    return path_to_cert;
}

const char* GCrypt::get_cert_into_buffer(int& len)
{
    len = strlen(cert_buf);
    return cert_buf;
}

int GCrypt::load_cert_from_file(const char * cert_path)
{
    FILE * pFile = fopen(cert_path, "rb");
    if (pFile == NULL)
    {
        perror("File error in GCrypt");
        return -1;
    }
    // obtain file size:
    fseek(pFile, 0, SEEK_END);
    int lSize = ftell(pFile);
    rewind(pFile);
    // allocate memory to contain the whole file:
    cert_buf = (char*) malloc(sizeof (char) * lSize + 1);
    if (cert_buf == NULL)
    {
        perror("Memory error in GCrypt");
        return -1;
    }
    // copy the file into the buffer:
    int result = fread(cert_buf, 1, lSize, pFile);
    if (result != lSize)
    {
        perror("Reading error in GCrypt");
        return -1;
    }
    cert_buf[lSize] = '\0';
    certWithTags = "<wlbs_handshake><certificate>" + std::string(cert_buf) + "</certificate></wlbs_handshake>";
    fclose(pFile);
    return 0;
}

int GCrypt::load_private_key_from_file(const char* private_key_path)
{
    FILE * fp = fopen(private_key_path, "r");
    if (fp == NULL)
    {
        perror("File error in GCrypt");
        return -1;
    }
    private_key = RSA_new();
    private_key = PEM_read_RSAPrivateKey(fp,NULL,NULL,NULL);
    if(private_key_path == NULL)
    {
        perror("RSA private key load error in GCrypt");
        return -1;
    }
    fclose(fp);
    return 0;
}

RSA * GCrypt::get_cert_public_key(const char * path_to_cert)
{
    X509 * cert = NULL;
    FILE * fp = NULL;
    fp = fopen(path_to_cert, "rb");
    if (fp == NULL)
    {
        perror("error open file");
        return NULL;
    }
    cert = PEM_read_X509(fp, NULL, NULL, NULL);
    if (cert == NULL)
    {
        perror("error reading certificate");
        return NULL;
    }
    EVP_PKEY *evpKey = X509_get_pubkey(cert);
    if (evpKey == NULL)
    {
        perror("error in evp key");
        return NULL;
    }
    RSA * rsa_public = EVP_PKEY_get1_RSA(evpKey);
    if(rsa_public == NULL)
    {
        perror("error in RSA key");
        return NULL;
    }
    return rsa_public;
}

int GCrypt::GetPrivateKeySize()
{
    return RSA_size(private_key);
}

int GCrypt::GetPublicKeySize()
{
    return RSA_size(pub_key);
}

//function to encrypt data with RSA.
//In : buffer to encrypt , length of buffer.
//Out : encrypted buffer
unsigned char * GCrypt::rsa_encrypt(unsigned char* buffer_to_enc, int buf_len)
{
    unsigned char* enc_buffer = (unsigned char *)malloc(RSA_size(pub_key));
    RSA_blinding_off(pub_key);
    int result;
    result = RSA_public_encrypt(buf_len,(unsigned char *)buffer_to_enc,(unsigned char*)enc_buffer,pub_key,RSA_PKCS1_PADDING);
    if (result < 0)
    {
        perror("Error in RSA encryption");
        return NULL;
    }

    return enc_buffer;
}

unsigned char * GCrypt::rsa_decrypt(unsigned char* buffer_to_dec, int buf_len)
{
//    int n = RSA_size(private_key);
//    for(int i = 0; i < n; i++)
//        cout<<i<<")"<<buffer_to_dec[i];
    unsigned char* dec_buffer = (unsigned char *)malloc(RSA_size(private_key));
    int result;
    result = RSA_private_decrypt(RSA_size(private_key),buffer_to_dec,dec_buffer,private_key,RSA_PKCS1_PADDING);
    if (result < 0)
    {
        perror("Error in RSA decryption");
        return NULL;
    }
    unsigned char* returnBuffer = (unsigned char *)malloc(buf_len);
    memcpy(returnBuffer, dec_buffer, buf_len);
    free(dec_buffer);
    return returnBuffer;
}
/*! \fn unsigned char * GCrypt::aes_encrypt(unsigned char* in_buffer,int buf_len,unsigned char* key) 
 * \brief Function to perform symmetric encryption. If input buffer is not multiple to 16 - it is increased in 1 to 15 bytes and several '\0' symbols are added to fill empty space.
 * \param in_buffer - data to encrypt
 * \param buf_len - length of  data buffer to encrypt
 * \param key - 32 byte key to encrypt data
 * \param enc_len - size of encrypted data
 * \return cipherText - encrypted data buffer 
 */
unsigned char * GCrypt::aes_encrypt(unsigned char* in_buffer,int buf_len,unsigned char* key, int* enc_len)
{
	unsigned char* toEncrypt;						//Pointer to data to be encrypted
	int encDataSize;								//Size of data to be encrypted
	//Data to encrypt must be multiple to 16, so if it is not - we add some '\0' symbols
	int emptyCells = 16 - buf_len%16;				//Number of '\0' symbols to add
	if (emptyCells)									//If data to encrypt is not multuple to 16
	{								
		encDataSize = buf_len + emptyCells;			//Size of data to be encrypted with '\0' symbols (multiple to 16)
		toEncrypt = (unsigned char*)malloc(encDataSize);
		memcpy(toEncrypt,in_buffer,buf_len);
		for(int i=0; i < emptyCells; i++)			//Add '\0' symbols to last block of data
			toEncrypt[buf_len + i] = (unsigned char)0;
	}
	else 	//If input data is multiple to 16
	{
		encDataSize = buf_len;
		toEncrypt = in_buffer;
	}
	unsigned char* cipherText = (unsigned char*)malloc(encDataSize);
    char iv[AES_BLOCK_SIZE*4]="0";

    AES_KEY aes_key;
    AES_set_encrypt_key((unsigned char*)key, 256, &aes_key);
    AES_cbc_encrypt(toEncrypt,cipherText,encDataSize,&aes_key,(unsigned char* )iv,AES_ENCRYPT);
	free(toEncrypt);
	*enc_len = encDataSize;
    return cipherText;
}

/*! \fn unsigned char * GCrypt::aes_decrypt(unsigned char* in_buffer,int buf_len,unsigned char* key)
 * \brief Function to decrypt data. If input buffer is not multiple to 16 - it is increased in 1 to 15 bytes and several '\0' symbols are added to fill empty space.
 * \param in_buffer - data to decrypt
 * \param buf_len - length of  data buffer to decrypt
 * \param key - 32 byte key to encrypt data
 * \return plainText - decrypted data buffer of size multiple to 16 (with added '\0' symbols) 
 */
unsigned char * GCrypt::aes_decrypt(unsigned char* in_buffer,int buf_len,unsigned char* key)
{
	unsigned char* toDecrypt;						//Pointer to data to be decrypted
	int decDataSize;								//Size of data to be decrypted
	/*//Data to decrypt must be multiple to 16, so if it is not - we add some '\0' symbols
	int emptyCells = 16 - buf_len%16;				//Number of '\0' symbols to add
	if (emptyCells)									//If data to decrypt is not multuple to 16
	{								
		decDataSize = buf_len + emptyCells;			//Size of data to be decrypted with '\0' symbols (multiple to 16)
		toDecrypt = (unsigned char*)malloc(decDataSize);
		memcpy(toDecrypt,in_buffer,buf_len);
		for(int i=0; i < emptyCells; i++)			//Add '\0' symbols to last block of data
			toDecrypt[buf_len + i] = (unsigned char)0;
	}
	else 	//If input data is multiple to 16
	{
		decDataSize = buf_len;
		toDecrypt = in_buffer;
	}*/
	decDataSize = buf_len;
	toDecrypt = in_buffer;
	
    unsigned char* plainText = (unsigned char*)malloc(decDataSize);
    char iv[AES_BLOCK_SIZE*4]="0";

    AES_KEY aes_key;
    AES_set_decrypt_key((unsigned char*)key, 256, &aes_key);
    AES_cbc_encrypt(toDecrypt,plainText,decDataSize,&aes_key,(unsigned char *)iv,AES_DECRYPT);
    //free(toDecrypt);
    return plainText;
}

unsigned char * GCrypt::generate_key()
{
    unsigned char * key = (unsigned char *)malloc(32);
    if(key==NULL)
        return 0;
    RAND_pseudo_bytes(key,32);
    return key;
}


